﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.IO;
using System.Windows.Forms;

using TraitorAmongUs.Exceptions;

namespace TraitorAmongUs.Network {
	public enum ServerStates {
		INITIALIZATION = 0,
		LOBBY = 1,
		GAME = 2
	}

	public class Server {
		TcpListener listener;
		Thread listeningThread;
		Thread serverLogicThread;

		ServerStates state;

		private object clientLock = new object();
		ArrayList clients;

		private object playerLock = new object();
		ServerPlayer[] players = new ServerPlayer[10];

		int _port;
		bool running;
		bool gameRunning;

		public Server() {
			clients = new ArrayList();
			running = false;

			state = ServerStates.INITIALIZATION;
		}

		public void Run(int port) {
			_port = port;
			if(!running) {
				listener = new TcpListener(IPAddress.Any, port);

				running = true;

				state = ServerStates.LOBBY;

				listeningThread = new Thread(new ThreadStart(ListeningThread));
				listeningThread.IsBackground = true;
				listeningThread.Start();
			}
		}

		public void Close() {
			running = false;
			gameRunning = false;

			lock(clientLock) {
				foreach(ServerClient client in clients) {
					client.tcpClient.Close();
				}
			}
		}

		void ServerLogicThread() {
			gameRunning = true;

			DateTime lastUpdateTime = DateTime.Now;
			long ticksSinceLastUpdate = 0;

			while(gameRunning) {
				ticksSinceLastUpdate = DateTime.Now.Ticks - lastUpdateTime.Ticks;
				TimeSpan elapsedSpan = new TimeSpan(ticksSinceLastUpdate);

				if(elapsedSpan.TotalMilliseconds < 30) continue;

				lastUpdateTime = DateTime.Now;

				// Check if someone won
				lock(playerLock) {
					int winner = -1;
					for(int i = 0; i < players.Length; ++i) {
						if(players[i].X >= 1024 - 32) {
							// Player i won
							winner = i;
							break;
						}
					}

					if(winner >= 0) {
						using(Packet winnerPacket = new Packet()) {
							winnerPacket.Write(string.Format("Player {0}", winner));

							lock(clientLock) {
								foreach(ServerClient client in clients) {
									winnerPacket.Send(client.tcpClient.GetStream(), Packet.PacketTypes.CLIENT_GAME_WINNER);
								}
							}
						}

						gameRunning = false;
						state = ServerStates.LOBBY;
						break;
					}
				}

				// Generate the update packet and update player positions
				using(Packet updatePacket = new Packet()) {
					Random randomGen = new Random();
					lock(playerLock) {
						for(int i = 0; i < players.Length; ++i) {
							if(!players[i].isHumanControlled && players[i].State != PlayerStates.DEAD) {
								// Generate a random state
								int randomChangeState = randomGen.Next(200000);

								if(randomChangeState >= 190000) {
									int randomStateVal = randomGen.Next(300);

									if(randomStateVal >= 150) {
										players[i].State = PlayerStates.IDLE;
									} else if(randomStateVal >= 50) {
										players[i].State = PlayerStates.WALKING;
									} /*else if(randomStateVal >= 25) {
										players[i].State = PlayerStates.DEAD;
									}*/ else {
										players[i].State = PlayerStates.RUNNING;
									}
								}
							}

							players[i].X +=
								(players[i].State == PlayerStates.WALKING ? 1 :
								(players[i].State == PlayerStates.RUNNING ? 2 :
								0));

							updatePacket.Write(players[i].X);
							updatePacket.Write(players[i].Y);
							updatePacket.Write((int)players[i].State);
						}
					}

					lock(clientLock) {
						foreach(ServerClient client in clients) {
							updatePacket.Send(client.tcpClient.GetStream(), Packet.PacketTypes.CLIENT_GAME_UPDATE_DATA);

							ArrayList crosshairs = new ArrayList();
							foreach(ServerClient c in clients) {
								if(c == client) continue;	// Don't include their own crosshair

								crosshairs.Add(c.crosshair);
							}

							using(Packet crosshairPacket = new Packet()) {
								crosshairPacket.Write(crosshairs.Count);

								foreach(Crosshair crosshair in crosshairs) {
									crosshairPacket.Write(crosshair.X);
									crosshairPacket.Write(crosshair.Y);
									crosshairPacket.Write(crosshair.r);
									crosshairPacket.Write(crosshair.g);
									crosshairPacket.Write(crosshair.b);
									crosshairPacket.Write(crosshair.bulletCount);
								}

								crosshairPacket.Send(client.tcpClient.GetStream(), Packet.PacketTypes.CLIENT_GAME_UPDATE_CROSSHAIR);
							}
						} // foreach(ServerClient client in clients)
					} // lock(clientLock)
				} // using(Packet updatePacket = new Packet())
			} // while(gameRunning)
		}

		void ListeningThread() {
			try {
				listener.Start();

				while(running) {
					// Only accept connections while in the lobby
					if(state != ServerStates.LOBBY) break;

					ServerClient newClient = new ServerClient();
					newClient.tcpClient = listener.AcceptTcpClient();

					// Only allow 10 people max
					if(clients.Count >= 10) {
						using(Packet packet = new Packet()) {
							packet.Send(newClient.tcpClient.GetStream(), Packet.PacketTypes.CLIENT_SERVERFULL);
						}

						newClient.tcpClient.Close();

						continue;
					}

					lock(clientLock) {
						newClient.isHost = false;
						clients.Add(newClient);
					}

					Thread clientThread = new Thread(new ParameterizedThreadStart(ClientThread));
					clientThread.IsBackground = true;
					clientThread.Start(newClient);
				}

				listener.Stop();
			} catch(Exception e) {
				MessageBox.Show(string.Format("Exception: {0}", e.Message));
			}
		}

		void ClientThread(object _client) {
			ServerClient client = (ServerClient)_client;
			NetworkStream clientStream = client.tcpClient.GetStream();

			client.crosshair = new Crosshair();
			client.crosshair.bulletCount = 2;

			while(true) {
				if(client.tcpClient.Connected) {
					//byte[] packet = null;
					using(Packet packet = new Packet()) {
						try {
							packet.ReadPacket(clientStream);
						} catch(HeaderMismatchException hme) {
							Engine.WriteLog(string.Format("Header does not match: {0}; From: {1}", hme.header, client.tcpClient.Client.RemoteEndPoint.ToString()), true);
							continue;
						} catch(ConnectionInterruptedException) {
							break;
						}

						if(Engine.LOG_PACKETS) {
							Engine.WriteLog(string.Format("Packet Received: {0}; From: {1}", packet.Type.ToString(), client.tcpClient.Client.RemoteEndPoint.ToString()), true);
						}

						if(state == ServerStates.LOBBY) ProcessLobbyPacket(client, packet);
						else if(state == ServerStates.GAME) ProcessGamePacket(client, packet);
					}
				} else {
					break;
				}
			}

			// Remove the client
			lock(clientLock) {
				clients.Remove(client);
			}

			client.tcpClient.Close();
		}

		void ProcessLobbyPacket(ServerClient client, Packet receivedPacket) {
			switch(receivedPacket.Type) {
				case Packet.PacketTypes.SERVER_LOBBY_SET_HOST: {
					client.isHost = true;
				} break;

				case Packet.PacketTypes.SERVER_LOBBY_CHATMESSSAGE: {
					string message = receivedPacket.ReadString();
					string username = client.tcpClient.Client.RemoteEndPoint.ToString();

					using(Packet packet = new Packet()) {
						packet.Write(username);
						packet.Write(message);

						lock(clientLock) {
							foreach(ServerClient _client in clients) {
								packet.Send(_client.tcpClient.GetStream(), Packet.PacketTypes.CLIENT_LOBBY_CHATMESSAGE);
							}
						}
					}
				} break;

				case Packet.PacketTypes.SERVER_LOBBY_START_GAME: {
					if(client.isHost) {	// Only the host can start the game
						// Fill the player data
						int[] randomPlayerNumbers = new int[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
						randomPlayerNumbers.Shuffle<int>();

						//Random rng = new Random();
						for(int i = 0; i < players.Length; ++i) {
							players[i] = new ServerPlayer();
							players[i].X = 0;
							players[i].Y = 64 * i;
							//players[i].State = (rng.Next(100) > 50 ? PlayerStates.IDLE : PlayerStates.DEAD);
							players[i].State = PlayerStates.IDLE;
							players[i].spritePath = @"Sprites\Sprite1"; // TODO: Randomize Sprite
						}

						lock(clientLock) {
							int j = 0;
							foreach(ServerClient _client in clients) {
								_client.playerNumber = randomPlayerNumbers[j];
								players[randomPlayerNumbers[j]].isHumanControlled = true;
								++j;

								using(Packet packet = new Packet()) {
									packet.Send(_client.tcpClient.GetStream(), Packet.PacketTypes.CLIENT_LOBBY_START_GAME);
								}
							}
						}

						state = ServerStates.GAME;
					}
				} break;
			}
		}

		void ProcessGamePacket(ServerClient client, Packet receivedPacket) {
			switch(receivedPacket.Type) {
				case Packet.PacketTypes.SERVER_GAME_LOADED: {
					using(Packet packet = new Packet()) {
						for(int i = 0; i < players.Length; ++i) {
							packet.Write(players[i].X);
							packet.Write(players[i].Y);
							packet.Write((int)players[i].State);
							packet.Write(players[i].spritePath);
						}

						packet.Send(client.tcpClient.GetStream(), Packet.PacketTypes.CLIENT_GAME_INIT_DATA);
					}

					using(Packet packet = new Packet()) {
						packet.Write(client.crosshair.bulletCount);
						packet.Send(client.tcpClient.GetStream(), Packet.PacketTypes.CLIENT_GAME_UPDATE_BULLETCOUNT);
					}
				} break;

				case Packet.PacketTypes.SERVER_GAME_UPDATE_CROSSHAIR_COLOR: {
					lock(clientLock) {
						client.crosshair.r = receivedPacket.ReadInt();
						client.crosshair.g = receivedPacket.ReadInt();
						client.crosshair.b = receivedPacket.ReadInt();
					}
				} break;

				case Packet.PacketTypes.SERVER_GAME_INIT_DATA_RECEIVED: {
					// Sent when the client has received the initial player data
					client.isLoaded = true;

					bool allLoaded = true;
					lock(clientLock) {
						foreach(ServerClient _client in clients) {
							if(!_client.isLoaded) {
								allLoaded = false;
								break;
							}
						}
					}

					if(allLoaded) {
						lock(clientLock) {
							foreach(ServerClient _client in clients) {
								using(Packet packet = new Packet()) {
									packet.Send(_client.tcpClient.GetStream(), Packet.PacketTypes.CLIENT_GAME_LOADED);
								}
							}
						}

						// Game started, start the server logic thread
						serverLogicThread = new Thread(new ThreadStart(ServerLogicThread));
						serverLogicThread.Start();
					}
				} break;

				case Packet.PacketTypes.SERVER_GAME_UPDATE_STATE: {
					// Only update the state if they aren't dead
					if(players[client.playerNumber].State != PlayerStates.DEAD) {
						players[client.playerNumber].State = (PlayerStates)receivedPacket.ReadInt();
					}
				} break;

				case Packet.PacketTypes.SERVER_GAME_UPDATE_CROSSHAIR: {
					client.crosshair.X = receivedPacket.ReadInt();
					client.crosshair.Y = receivedPacket.ReadInt();
				} break;

				case Packet.PacketTypes.SERVER_GAME_SHOOT: {
					int x = receivedPacket.ReadInt();
					int y = receivedPacket.ReadInt();

					bool livingPlayerExists = false;	// Check if there is still a living player; if not, the game is over

					if(client.crosshair.bulletCount > 0) {
						lock(playerLock) {
							foreach(var player in players) {
								if(x >= player.X && x <= player.X + player.Width) {
									if(y >= player.Y && y <= player.Y + player.Height) {
										// This player was shot
										player.State = PlayerStates.DEAD;
									}
								}

								if(player.State != PlayerStates.DEAD) livingPlayerExists = true;
							}

							if(!livingPlayerExists) {
								// The game is a tie, all players are dead
								lock(clientLock) {
									using(Packet packet = new Packet()) {
										packet.Write("No One");

										foreach(ServerClient _client in clients) {
											packet.Send(_client.tcpClient.GetStream(), Packet.PacketTypes.CLIENT_GAME_WINNER);
										}
									}
								}

								break;
							}
						}

						--client.crosshair.bulletCount;
					}

					using(Packet packet = new Packet()) {
						packet.Write(client.crosshair.bulletCount);
						packet.Send(client.tcpClient.GetStream(), Packet.PacketTypes.CLIENT_GAME_UPDATE_BULLETCOUNT);
					}
				} break;
			}
		}
	}
}
